package server;

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;

import javax.net.ssl.SSLHandshakeException;

import protocol.Info;

import com.google.common.collect.Lists;

import data.Blind;
import data.VotingCard;
import data.VotingSet;
import database.ConnectionHandler;

public class VoteWorker implements Callable
{
    private Socket socket;
    private Object lock = new Object();

    public VoteWorker( Socket socket )
    {
        this.socket = socket;
        // SSLSession session = ((SSLSocket)this.socket).getSession();
        // try{
        // X509Certificate[] chain = session.getPeerCertificateChain();
        // for (X509Certificate cert : chain){
        // System.out.println("VoteWorker: " + cert.getSubjectDN());
        // }
        // } catch (Exception e){
        // e.printStackTrace();
        // }
    }

    @Override
    public Object call() throws SSLHandshakeException
    {
        InputStream in;
        ObjectInputStream oin = null;
        Object received;
        OutputStream out;
        ObjectOutputStream oout = null;
        try
        {
            in = socket.getInputStream();
            oin = new ObjectInputStream( in );
            out = socket.getOutputStream();
            oout = new ObjectOutputStream( out );
            received = oin.readObject();
            String voterPesel = (String)received;
            received = oin.readObject();
            List<String> candidates = Lists.newLinkedList();
            if( received instanceof String )
            {
                String message = (String)received;
                if( message.equals( Info.MSG_CANDIDATES ) )
                {
                    System.out.println( "Server: dostalem prosbe o kandydatow." );
                    // pobieram kandydatow z bazy
                    System.out.println( "Server: pobieram kandydatow z bazy." );
                    ConnectionHandler connHandler = new ConnectionHandler();
                    VotingCard card = new VotingCard( false );
                    Statement st = connHandler.getConnection().createStatement();
                    ResultSet rs = st.executeQuery( "SELECT * from candidate" );
                    while( rs.next() )
                    {
                        String name = rs.getString( 1 ) + " " + rs.getString( 2 ) + " " + rs.getString( 3 );
                        card.addCandidate( name );
                        candidates.add( name );
                    }
                    System.out.println( "VoteWorker: pobralem kandydatow -> " + candidates );
                    oout.writeObject( Info.MSG_CANDIDATES + card.toString() );
                    connHandler.closeConnection();
                }
            }
            // read blinded cards
            List<String> blindedSets = Lists.newLinkedList();
            for( int i = 0; i < 100; i++ )
                blindedSets.add( (String)oin.readObject() );
            int randomServerNumber = new Random().nextInt( 99 ) + 1;
            oout.writeObject( String.valueOf( randomServerNumber ) );
            received = oin.readObject();
            String[] serverBlindParameters = ( (String)received ).split( "\r\n" );
            Blind b = new Blind();
            for( int i = 0; i < 100; i++ )
            {
                if( i == randomServerNumber )
                    continue;
                int parameterNumber = i;
                if( i > randomServerNumber )
                    parameterNumber--;
                BigInteger blindParameter = new BigInteger( serverBlindParameters[ parameterNumber ] );
                String blindSet = blindedSets.get( i );
                blindSet = b.sign( blindSet, VoteServer.PKWPrivateKey );
                blindSet = b.unblind( blindSet, VoteServer.PKWPublicKey, blindParameter );
                VotingSet unblindedSet = b.unsignSet( blindSet, VoteServer.PKWPublicKey );
                if( !unblindedSet.isValid( candidates ) )
                {
                    System.out.println( unblindedSet.isValid( candidates ) );
                    oout.writeObject( Info.MSG_ERROR_BLINDING );
                    return null;
                }
                // SET IS OK
            }
            // ALL SETS ARE OK
            System.out.println( "All sets are valid!" );
            synchronized( lock )
            {
                try
                {
                    ConnectionHandler connHandler = new ConnectionHandler();
                    Statement st = connHandler.getConnection().createStatement();
                    ResultSet rs = st.executeQuery( "SELECT * from voter where pesel='" + voterPesel + "' and hascard='t'" );
                    if( rs.next() )
                    {
                        // CHEATER!!!!!!!
                        // System.out.println( "You've already voted!" );
                        return null;
                    }
                    st = connHandler.getConnection().createStatement();
                    st.executeUpdate( "UPDATE voter set hascard='t' where pesel='" + voterPesel + "'" );
                    connHandler.closeConnection();
                }
                catch( Exception e )
                {
                    throw new RuntimeException( e );
                }
            }
            String selectedSet = blindedSets.get( randomServerNumber );
            String selectedSignedSet = b.sign( selectedSet, VoteServer.PKWPrivateKey );
            oout.writeObject( selectedSignedSet );
            System.out.println( "Client has downloaded vote!" );
        }
        catch( IOException e )
        {
            e.printStackTrace();
        }
        catch( ClassNotFoundException e )
        {
            e.printStackTrace();
        }
        catch( SQLException e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        finally
        {
            try
            {
                if( oin != null )
                    oin.close();
                if( oout != null )
                    oout.close();
                if( socket != null )
                    socket.close();
            }
            catch( IOException e )
            {
                e.printStackTrace();
            }
        }
        return null;
    }
}
